using System;
using System.Collections.Generic;
using System.ComponentModel;

using System.Drawing;
using System.Text;
using System.Windows.Forms;
using CodePlex.SpaceBlock;
using CodePlex.SpaceBlock.FS;
using System.Threading;
using CodePlex.Resourceful;
using CodePlex.Resourceful.Amazon.S3;
using System.Net;
using CodePlex.SpaceBlock.JobModel;
using CodePlex.SpaceBlock.UI.Controls.Transfer;

namespace CodePlex.SpaceBlock.UI.Controls.Explore
{
    public partial class TransferForm : CodePlex.SpaceBlock.UI.Controls.BaseForm
    {
        private static class Actions
        {
            public static readonly string ClearSelection = "ClearSelection";
            public static readonly string CannedACL = "CannedACL";
            public static readonly string ContentType = "ContentType";
            public static readonly string TransferMetadata = "TransferMetadata";
        }

        private class SystemAndUserMetadata
        {
            public NameValuePairs SystemMetadata = new NameValuePairs();
            public NameValuePairs UserMetadata = new NameValuePairs();

            public override string ToString()
            {
                bool comma = SystemMetadata.Count > 0 && UserMetadata.Count > 0;
                return SystemMetadata.ToString() + (comma?",":"") + UserMetadata.ToString();
            }
        }

        public event EventHandler TransferCompleted;

        public TransferForm()
        {
            InitializeComponent();
        }

        public void Initialize(IFileSystem from, List<Path> fromPaths, IFileSystem to, Path toFolder)
        {


           

            this.Icon = UIManager.Instance.GetIcon(UIConstants.ImageHints.Transfer);


            this.Status("Initializing transfer...");
            this.Title("Initializing transfer...");

            this.Speed("");
            this.Amount("");


            Thread transferThread = null;

            _cancelButton.Click += delegate
            {
                if (transferThread != null)
                {
                    transferThread.Abort();
                }
                this.Dispose();
            };


            _transferButton.Enabled = false;


             List<IFileItem> fromFiles = null;
             List<Path> fromFolders = null;


             _fromBox.Text = from.Caption;
             _toBox.Text = toFolder.ToString();

            DataGridView2 grid = new DataGridView2();
            bool toIsS3 = (to is S3FileSystem);
            ISupportsTransferWithMetadata toSupportsMetadata = to as ISupportsTransferWithMetadata;



            ActionManager actions = new ActionManager(this, null, _contextMenu, null);

            // set up context menu
            if (toSupportsMetadata != null)
            {
                
                grid.ContextMenuStrip = _contextMenu;

                if (toIsS3)
                {
            

                    actions.NewAction(Actions.ContentType, "Set Content-Type", 0, Keys.None, delegate
                    {

                    });

                    foreach (string contentTypeCategory in SpaceBlockUtils.GetContentTypeCategories())
                    {

                        string categoryName = Actions.ContentType + "." + contentTypeCategory;

                        actions.NewSubAction(Actions.ContentType, categoryName, contentTypeCategory, 0, Keys.None, delegate
                        {

                        });

                        foreach (string contentType in SpaceBlockUtils.GetContentTypes(contentTypeCategory))
                        {
                            string localContentType = contentType;
                            string name = categoryName + "." + contentType;

                            actions.NewSubAction(categoryName, name, contentType, 0, Keys.None, delegate
                            {

                                actions.SetChecked(Actions.ContentType, false);
                                actions.SetChecked(name, true);

                                foreach (DataGridViewRow row in grid.SelectedRows)
                                {
                                    if (row.Tag is IFileItem)
                                    {
                                        row.Cells[2].Value = localContentType;
                                    }
                                }

                            });
                        }

                    }


                    actions.NewAction(Actions.CannedACL, "Set Canned ACL", 0, Keys.None, delegate
                    {

                    });

                    foreach (string cannedACL in S3Constants.CannedAccessPolicies.All)
                    {

                        string localCannedACL = cannedACL;
                        string name = Actions.CannedACL + "." + cannedACL;

                        actions.NewSubAction(Actions.CannedACL, name, cannedACL, 0, Keys.None, delegate
                        {
                            actions.SetChecked(Actions.CannedACL, false);
                            actions.SetChecked(name, true);

                            foreach (DataGridViewRow row in grid.SelectedRows)
                            {
                                if (row.Tag is IFileItem)
                                {
                                    row.Cells[3].Value = localCannedACL;
                                }
                            }
                        });



                    }

                }

                TransferMetadataForm form = new TransferMetadataForm();
                form.Icon = this.Icon;

                actions.NewAction(Actions.TransferMetadata, "Edit Metadata...", 0, Keys.None, delegate
                {


                    int index = toIsS3 ? 4 : 2;

                    SystemAndUserMetadata firstSelected = grid.SelectedRows[0].Cells[index].Value as SystemAndUserMetadata;

                    form.Initialize(firstSelected.SystemMetadata,(toIsS3?UnencodeS3( firstSelected.UserMetadata):firstSelected.UserMetadata));

                    if (form.ShowDialog(this) == DialogResult.OK)
                    {

                        SystemAndUserMetadata m = new SystemAndUserMetadata();
                        m.SystemMetadata = form.SystemMetadata;
                        m.UserMetadata = toIsS3 ? EncodeS3(form.UserMetadata) : form.UserMetadata;
                        


                        foreach (DataGridViewRow row in grid.SelectedRows)
                        {
                            if (row.Tag is IFileItem)
                            {
                                row.Cells[index].Value = m;
                            }
                        }
                    }

                });

                actions.NewAction(Actions.ClearSelection, "Clear Selection", 0, Keys.None, delegate
                {
                    grid.ClearSelection();
                });

              

            }

            this.InBackground(delegate
            {

                fromFiles = new List<IFileItem>();
                fromFolders = new List<Path>();

                Action<Path> add = null;
                
                add = delegate(Path folderPath)
                {
                    fromFolders.Add(folderPath);

                    foreach (IPathItem item in from.GetItems(folderPath))
                    {
                        if (item.Path.IsFolder)
                        {
                            add(item.Path);
                        }
                        else
                        {
                            fromFiles.Add(item as IFileItem);
                        }
                    }
                };


                Path basePath = null;
                foreach(Path fromPath in fromPaths) {

                    basePath = basePath ?? fromPath.Parent;

                    if (fromPath.IsFolder)
                    {
                        add(fromPath);
                    }
                    else
                    {
                        fromFiles.Add(from.GetItem(fromPath) as IFileItem);
                    }
                }

                this.InDisplay(delegate
                {
                    
                    DataGridViewColumn col = grid.AddColumnText("", 300);
                    col.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;

                    if (toSupportsMetadata != null)
                    {
                        if (toIsS3)
                        {
                            grid.AddColumnText("Content Type", 150);
                            grid.AddColumnText("Canned ACL", 150);
                        }
                        grid.AddColumnText("Metadata", 150);
                    }
                   
                    foreach (Path fromFolder in fromFolders)
                    {
                        string to2 = fromFolder.ToString().Substring(basePath.ToString().Length);

                        List<object> values = new List<object>();
                        values.Add(UIManager.Instance.GetSmallImage(UIConstants.ImageHints.FolderClosed));
                        values.Add(to2);
                        if (toSupportsMetadata != null)
                        {
                            if (toIsS3)
                            {
                                values.Add("");
                                values.Add("");
                            }
                            values.Add("");
                        }
                        
                        grid.AddRow(fromFolder, values.ToArray());
                    }


                    string cannedAccessPolicy = S3Constants.CannedAccessPolicies.Private;
                    if (toIsS3)
                    {
                        // determine which canned access policy to display by default (private or public-read)
                        S3DefaultTransferACL defaultAcl = ((S3FileSystem)to).DefaultTransferACL;
                        if (defaultAcl.Equals(S3DefaultTransferACL.PublicRead))
                        {
                            cannedAccessPolicy = S3Constants.CannedAccessPolicies.PublicRead;
                        }
                        else if (defaultAcl.Equals(S3DefaultTransferACL.Bucket))
                        {
                            try
                            {
                                string bucket = toFolder.Parts[0];

                                ACL bucketACL = ((S3FileSystem)to).S3Connection.GetACL(bucket);

                                if (bucketACL.GetPermissions(S3Constants.Principals.AllUsers).Contains(S3Constants.Permissions.Read)
                                    || bucketACL.GetPermissions(S3Constants.Principals.AllUsers).Contains(S3Constants.Permissions.FullControl)
                                    )
                                {
                                    cannedAccessPolicy = S3Constants.CannedAccessPolicies.PublicRead;
                                }
                            }
                            catch { }
                        }

                    }



                    foreach (IFileItem fromFile in fromFiles)
                    {
                        string to2 = fromFile.Path.ToString().Substring(basePath.ToString().Length);

                        List<object> values = new List<object>();
                        values.Add(UIManager.Instance.GetSmallImage(UIConstants.ImageHints.File));
                        values.Add(to2);
                        if (toSupportsMetadata != null)
                        {
                            if (toIsS3)
                            {
                                values.Add(MimeMap.Get(fromFile.Path.ToString()));
                                values.Add(cannedAccessPolicy);
                            }
                            values.Add(new SystemAndUserMetadata());
                        }

                        
                        grid.AddRow(fromFile, values.ToArray());
                    }

                    grid.Sort(grid.Columns[1], ListSortDirection.Ascending);
                    UIUtils.FillWith(_itemsHolder, grid);

                    this.Status("Ready.");
                    
                    this.Title("Transfer ready to {0}.", to.Caption);

                    _transferButton.Enabled = true;

                });


            });



            // new transfer code

            _transferButton.Click += delegate
            {
            
                
                TransferJob job = new TransferJob(to);
                foreach (DataGridViewRow row in grid.Rows)
                {


                    string part = row.Cells[1].Value as string;

                    Path toPath = toFolder.Combine(part);

                    // upload folder
                    Path fromFolder = row.Tag as Path;
                    if (fromFolder != null)
                    {
                        job.AddFolder(from,fromFolder, toPath);
                        continue;
                    }


                    // upload file
                    IFileItem fromFile = row.Tag as IFileItem;
                    if (fromFile == null)
                    {
                        continue;
                    }

                    NameValuePairs metadata = new NameValuePairs();
                    if (toSupportsMetadata != null)
                    {

                        int index = toIsS3 ? 4 : 2;

                        if (toIsS3)
                        {
                            // x -> s3
                            metadata[HttpHeader.ContentType] = row.Cells[2].Value as string;
                            metadata[S3Constants.Headers.CannedAccessPolicy] = row.Cells[3].Value as string;
                        }

                        SystemAndUserMetadata meta = row.Cells[index].Value as SystemAndUserMetadata;
                        if (meta != null)
                        {
                            metadata.AddRange(meta.SystemMetadata);
                            metadata.AddRange(meta.UserMetadata);
                        }
                        

                    }


                    job.AddFile(fromFile, toPath, metadata);


                }

                Guid jobID = UIManager.Instance.JobManager.Enqueue(job);

                this.Dispose();

                UIManager.Instance.MainForm.ShowView<TransfersView>();
                UIManager.Instance.MainForm.GetView<TransfersView>().ControlStrong.Select(jobID);



            };



        }















        private NameValuePairs EncodeS3(NameValuePairs unencoded)
        {
            NameValuePairs rt = new NameValuePairs();
            foreach (string key in unencoded.Keys)
            {
                string value = unencoded[key];

                rt[S3Constants.Headers.AmazonUserMetadataPrefix + key] = value;
            }
            return rt;
        }

        private NameValuePairs UnencodeS3(NameValuePairs encoded)
        {
            NameValuePairs rt = new NameValuePairs();
            foreach (string key in encoded.Keys)
            {
                string value = encoded[key];

                rt[key.Substring(S3Constants.Headers.AmazonUserMetadataPrefix.Length)] = value;
            }
            return rt;
        }












        // TODO remove
        private void OldTransferCode()
        {

            ActionManager actions = null;
            Thread transferThread = null;
            List<IFileItem> fromFiles = null;
            IFileSystem to = null;
            DataGridView grid = null;
            bool toIsS3 = (to is S3FileSystem);
            ISupportsTransferWithMetadata toSupportsMetadata = to as ISupportsTransferWithMetadata;
            Path toFolder = null;


            // old transfer code
            bool done = false;

            _transferButton.Click += delegate
            {
                if (done)
                {
                    this.Dispose();
                    return;
                }

                _transferButton.Enabled = false;
                _overallProgressBar.Value = 0;


                actions.SetEnabled(Actions.CannedACL, false);
                actions.SetEnabled(Actions.ContentType, false);
                actions.SetEnabled(Actions.TransferMetadata, false);
                actions.SetEnabled(Actions.ClearSelection, false);

                transferThread = this.InBackground(delegate
                {

                    long bytesTotal = 0L;
                    long bytesTransferred = 0L;


                    Utils.ForEach(fromFiles, delegate(IFileItem item)
                    {
                        bytesTotal += item.Size;
                    });


                    DateTime timeStart = DateTime.Now;

                    Action<long> updateProgress = delegate(long current)
                    {
                        this.InDisplay(delegate
                        {

                            string prefix = "Transferring...";
                            if (done)
                            {
                                prefix = "Transfer complete.";
                            }


                            long accurate = bytesTransferred + current;

                            double pct = 0D;

                            if (bytesTotal > 0)
                            {
                                pct = (double)accurate / (double)bytesTotal;
                            }

                            // some fs may xfer more bytes!
                            pct = Math.Min(pct, 1D);

                            _overallProgressBar.Value = (int)(pct * (double)_overallProgressBar.Maximum);

                            TimeSpan elapsed = DateTime.Now - timeStart;

                            double bps = (double)(accurate) / elapsed.TotalSeconds;

                            if (bps < 0)
                            {
                                bps = 0;
                            }

                            this.Status(prefix);
                            this.Speed("{0}/s", Utils.ComputeSizeString((long)bps));
                            this.Amount("{0}/{1}",
                                Utils.ComputeSizeString(accurate),
                                Utils.ComputeSizeString(bytesTotal));

                            if (done)
                            {
                                pct = 1;
                                _overallProgressBar.Value = _overallProgressBar.Maximum;
                            }

                            this.Title("{0:0%} complete to {1}.", pct, to.Caption);

                        });
                    };


                    foreach (DataGridViewRow row in grid.Rows)
                    {

                        string part = row.Cells[1].Value as string;

                        Path toPath = toFolder.Combine(part);

                        // upload folder
                        Path fromFolder = row.Tag as Path;
                        if (fromFolder != null)
                        {
                            to.EnsureFolderExists(toPath);
                            continue;
                        }


                        // upload file
                        IFileItem fromFile = row.Tag as IFileItem;
                        if (fromFile == null)
                        {
                            continue;
                        }

                        NameValuePairs metadata = new NameValuePairs();
                        if (toSupportsMetadata != null)
                        {
                            if (toIsS3)
                            {
                                // x -> s3
                                metadata[HttpHeader.ContentType] = row.Cells[2].Value as string;
                                metadata[S3Constants.Headers.CannedAccessPolicy] = row.Cells[3].Value as string;

                                Metadata user = row.Cells[4].Value as Metadata;
                                if (user != null)
                                {
                                    foreach (string key in user.Keys)
                                    {
                                        metadata[S3Constants.Headers.AmazonUserMetadataPrefix + key] = user[key];
                                    }
                                }

                                // md5 (pre)
                                this.InDisplay(delegate
                                {
                                    this.Status("Calculating hash...");
                                });
                                metadata[HttpHeader.ContentMD5] = Utils.ToBase64(fromFile.QuickMD5);

                            }
                            else
                            {
                                Metadata user = row.Cells[2].Value as Metadata;
                                if (user != null)
                                {
                                    foreach (string key in user.Keys)
                                    {
                                        metadata[key] = user[key];
                                    }
                                }
                            }

                        }

                        try
                        {

                            if (toSupportsMetadata != null)
                            {
                                toSupportsMetadata.Transfer(fromFile, toPath, metadata, updateProgress);
                            }
                            else
                            {
                                to.Transfer(fromFile, toPath, updateProgress);
                            }
                        }
                        catch (WebException ex)
                        {
                            // hide exception box if user cancelled...
                            if (ex.Message.Contains("The request was canceled"))
                            {
                                return;
                            }
                            throw;
                        }
                        bytesTransferred += fromFile.Size;

                    }


                    transferThread = null;
                    this.InDisplay(delegate
                    {
                        done = true;
                        updateProgress(0);
                        _transferButton.Text = "Close";
                        _transferButton.Enabled = true;
                        _cancelButton.Enabled = false;
                        OnTransferCompleted();

                    });

                });

            };
        }




        private void Title(string message, params object[] parameters)
        {
            this.Text = string.Format(message, parameters);
        }

        private void Status(string message, params object[] parameters)
        {
            _statusBox.Text = string.Format(message, parameters);
        }

        private void Speed(string message, params object[] parameters)
        {
            _speedBox.Text = string.Format(message, parameters);
        }

        private void Amount(string message, params object[] parameters)
        {
            _amountBox.Text = string.Format(message, parameters);
        }

        protected void OnTransferCompleted()
        {
            if (this.TransferCompleted != null)
            {
                this.TransferCompleted(this, EventArgs.Empty);
            }
        }


    }
}

